MP#03: Visualizing and Maintaining the Green Canopy of NYC
Author
Rashika Auti
Urban Roots: A Data-Driven Look
Introduction
New York City’s parks and green spaces are a vital part of the urban landscape, maintained by the Department of Parks and Recreation with over 5,000 employees and nearly 900,000 trees across more than 30,000 acres. This project explores the NYC TreeMap dataset to create clear, informative visualizations, and uses these insights to propose a new program aimed at maximizing the benefits of the city’s urban trees for all residents.
Data Acquisition
The analysis relies on two primary datasets: the NYC City Council District boundaries and the NYC Tree Points. The district boundaries are obtained as a static shapefile, while the tree locations are retrieved programmatically via the NYC OpenData API using a controlled, paginated approach. Together, these datasets provide the essential spatial framework for subsequent analyses.
Dataset#1: NYC City Council District Boundaries
To analyze tree distribution across New York City, it is essential to first obtain the geographic boundaries of the 51 City Council districts. These boundaries provide the spatial framework necessary to aggregate and interpret tree data at the district level.
View R Code
if(!dir.exists(file.path("data", "mp03"))){dir.create(file.path("data", "mp03"), showWarnings=FALSE, recursive=TRUE)}get_council_districts_wgs84 <-function() {City_Council_Clipped_to_Shoreline <-file.path("data", "mp03", "City_Council_Clipped_to_Shoreline.zip")if(!file.exists(City_Council_Clipped_to_Shoreline)){download.file("https://s-media.nyc.gov/agencies/dcp/assets/files/zip/data-tools/bytes/city-council/nycc_25c.zip", destfile=City_Council_Clipped_to_Shoreline, headers =c(`User-Agent`="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0") )}# Unzipping the file using unzip commandunzip(City_Council_Clipped_to_Shoreline, exdir =file.path("data", "mp03"))# Reading the shp file using sf::st_read commandlibrary(sf)# Path to the unzipped folder containing the shapefileshp_dir <-"data/mp03/nycc_25c"# List all files in the folder to identify the .shp filelist.files(shp_dir)# Read the shapefileshp_file <-file.path(shp_dir, "nycc.shp")council_districts <-st_read(shp_file)# Transform the result to WGS 84st_transform(council_districts, crs ="WGS84")}council_districts_wgs84 <-get_council_districts_wgs84()
Next, we display the collected data as a dataframe for easier interpretation.
The geometry column in an sf dataframe does not contain text or numeric values; instead, it stores complex spatial objects (MULTIPOLYGONs) that define the boundaries of each district. Since these geometries cannot be visually rendered within a standard dataframe, they appear as placeholders in the output. Next, we visualize the spatial data using the ggplot2 package to better understand the geographic distribution of the council districts.
View R Code
library(ggplot2)ggplot(council_districts_wgs84) +geom_sf(fill ="NA", color ="black") +theme_minimal() +theme(plot.background =element_rect(fill ="#E6F0FA"), # background around the entire plotpanel.grid.major =element_line(color ="grey80", size =0.3), # slightly visible major grid linespanel.grid.minor =element_line(color ="grey90", size =0.2), # subtle minor grid linesplot.title =element_text(hjust =0.5) ) +labs(title ="NYC Council District Boundaries")
Full-resolution geometry captures every detail of district boundaries, but plotting large spatial datasets can be slow. By using the dTolerance parameter in st_simplify(), we can simplify these boundaries, speeding up rendering while preserving the overall shapes. This makes visualizations more efficient without losing important spatial context.
View R Code
# Re-rendering the plot with reduced resolution using st_simplify() and the dTolerance parameter set to 50library(dplyr)library(ggplot2)library(sf)ggplot(council_districts_wgs84 |>mutate(geometry =st_simplify(geometry, dTolerance =50))) +geom_sf(fill ="NA", color ="black") +theme_minimal() +theme(plot.background =element_rect(fill ="#E6F0FA"), # background around the entire plotpanel.grid.major =element_line(color ="grey80", size =0.3), # slightly visible major grid linespanel.grid.minor =element_line(color ="grey90", size =0.2), # subtle minor grid linesplot.title =element_text(hjust =0.5) ) +labs(title ="NYC Council District Boundaries (Reduced Resolution)", hjust =0.5)
Dataset#2: NYC Tree Points
To assess the distribution, species, and characteristics of urban trees, the NYC Tree Points dataset provides detailed geospatial information for nearly 900,000 trees across the city. This dataset is accessed via the NYC OpenData API and forms the foundation for all tree-level analyses.
This code downloads the NYC Tree Points dataset in manageable chunks to avoid overloading the server. The get_nyc_tree_points() function fetches data in batches, controlled by limit (how many trees per request) and offset (where to start each batch). Each batch is saved locally and read into R with st_read() from the sf package, then combined using bind_rows(). The httr2 functions handle the API call responsibly, with retries and a custom User-Agent. By enabling chunk caching (#| cache: true), this process runs only once, speeding up website rendering.
View R Code
##| cache: false # avoid caching large objects in knitrlibrary(httr2) # API callslibrary(sf) # spatial datalibrary(dplyr) # data manipulation# Directory to store raw data and final RDSnyc_tree_points_raw_data <-"data/mp03"if(!dir.exists(nyc_tree_points_raw_data)){dir.create(nyc_tree_points_raw_data, recursive =TRUE, showWarnings =FALSE)}# RDS file path to save/load entire datasettree_rds_file <-file.path(nyc_tree_points_raw_data, "nyc_tree_points.rds")# Function to download Tree Points data in batchesget_nyc_tree_points <-function(limit =50000, nyc_tree_points_raw_data ="data/mp03") { url <-"https://data.cityofnewyork.us/resource/hn5i-inap.geojson" combined_data <-list() offset <-0 batch_num <-1repeat {# Temporary file path for each batch interim_file <-file.path(nyc_tree_points_raw_data, paste0("tree_points_batch_", batch_num, ".geojson"))# Download batch only if file doesn't existif(!file.exists(interim_file)) {request(url) |>req_url_query(`$limit`= limit, `$offset`= offset) |>req_headers(`User-Agent`="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0") |>req_retry(max_tries =5) |>req_perform(path = interim_file) }# Read GeoJSON batch batch_data <-st_read(interim_file, quiet =TRUE)# Stop if batch is empty (end of dataset)if(nrow(batch_data) ==0) break combined_data[[batch_num]] <- batch_data# Update offset and batch index offset <- offset +nrow(batch_data) batch_num <- batch_num +1 }# Combine all batches into a single sf object nyc_tree_points <-bind_rows(combined_data)return(nyc_tree_points)}# Load data: either from saved RDS or download if not presentif (!file.exists(tree_rds_file)) { nyc_tree_points <-get_nyc_tree_points()# Save the complete dataset for future usesaveRDS(nyc_tree_points, tree_rds_file)} else { nyc_tree_points <-readRDS(tree_rds_file)}
To get a quick glimpse of the NYC Tree Points dataset, we display a random sample of 50 observations using an interactive table below.
Before preparing the final report, it’s helpful to visually explore how trees are distributed across New York City. This allows us to gain an initial understanding of spatial patterns, identify areas with dense or sparse tree coverage, and uncover potential insights for further analysis.
Mapping NYC Trees
In this step, we will create a ggplot2 map that overlays all tree locations across New York City on top of the City Council district boundaries.
This visualization involves layering two spatial elements: the district boundaries (POLYGONs) and individual tree points (POINTs); using multiple geom_sf() layers within ggplot2. Each layer will take its own dataset and mapping arguments, allowing us to combine different spatial geometries in one coherent map. Since NYC has a large number of trees, we will fine-tune the plot’s appearance (e.g., transparency, point size) to ensure the visualization remains clear and readable.
View R Code
library(ggplot2)library(sf)library(dplyr)# Add dummy variables for legenddistricts_legend <- council_districts_wgs84 |>mutate(geometry =st_simplify(geometry, dTolerance =50),Layer ="Council District")trees_legend <- nyc_tree_points |>mutate(Layer ="Tree Point")# Layered plot with legendggplot() +# Layer 1: NYC Council District Boundaries (POLYGON)geom_sf(data = districts_legend, aes(color = Layer), fill =NA,size =0.5, alpha =0.7) +# Layer 2: NYC Tree Points (POINT)geom_sf(data = trees_legend, aes(color = Layer), size =0.3, alpha =0.3) +# Color mapping for legendscale_color_manual(values =c("Council District"="black", "Tree Point"="lightgreen")) +# Theme adjustmentstheme_minimal() +theme(panel.grid.major =element_line(color ="grey80", size =0.3), # slightly visible major grid linespanel.grid.minor =element_line(color ="grey90", size =0.2), # subtle minor grid linesplot.title =element_text(size =12, face ="bold", hjust =0.5),plot.subtitle =element_text(size =9, hjust =0.5),legend.position ="bottom" ) +# Titleslabs(title ="NYC Tree Points Across Council Districts",subtitle ="Trees superimposed over the City Council district boundaries",color ="Layer" )
The resulting map shows all NYC trees overlaid on City Council districts. Black borders outline the districts, while small green points represent trees. Transparency (alpha) highlights areas with more trees, making dense clusters visible without clutter. The clean layout and titles make it easy to see how tree distribution varies across districts.
District-Level Analyses of Trees (Spatial join)
Next, we want to understand how trees are distributed across different City Council districts in NYC. To do this, we need to connect each tree to the district it belongs to. Unlike simple table joins, this requires a spatial join because we are matching points (trees) to polygons (districts). Using the st_join() function from the sf package, we can assign trees to districts based on their location, which sets the stage for district-level summaries and visualizations.
When using st_contains(), the polygon must be the first argument and the points the second; and vice-versa for st_intersects().
View R Code
##| cache: false # avoid caching in knitr#| output: falselibrary(sf)library(dplyr)# File path to save/load the spatial join resultspatial_join_file <-file.path("data/mp03", "nyc_trees_districts_joined.rds")if (!file.exists(spatial_join_file)) {# Perform the spatial join (points that lie within simplified polygons) nyc_trees_districts_joined <-st_join( nyc_tree_points, council_districts_wgs84,join = st_within )# Save the result for future usesaveRDS(nyc_trees_districts_joined, spatial_join_file)} else {# Load precomputed spatial join nyc_trees_districts_joined <-readRDS(spatial_join_file)}# Quick preview# head(nyc_trees_districts_joined)
Let us explore a few sample data points generated from our spatial join to get a sense of the dataset using an interactive datatable.
Next, we quantify tree distribution by district. Each tree point is assigned to its respective City Council district, and the total number of trees per district is calculated. These results are then visualized as a choropleth map, where districts with more trees appear green and those with fewer trees appear yellow. This color gradient provides an intuitive view of tree density, making it easy to identify areas with dense tree coverage and districts that may benefit from additional planting, offering a clear picture of NYC’s urban forest distribution.
View R Code
library(ggplot2)library(dplyr)library(sf)library(RColorBrewer)# Count number of trees per district (tree density)tree_density <- nyc_trees_districts_joined |>st_drop_geometry() |>group_by(CounDist) |>summarise(Tree_Count =n())# Merge tree density with council district geometrycouncil_with_density <- council_districts_wgs84 |>left_join(tree_density, by ="CounDist") |>mutate(Tree_Count =ifelse(is.na(Tree_Count), 0, Tree_Count))# Plot with light blue background and subtle gridlinesggplot(council_with_density) +geom_sf(aes(fill = Tree_Count), color ="white", size =0.3) +scale_fill_distiller(palette ="YlGn", direction =1, name ="Number of Trees") +theme_minimal() +theme(panel.grid.major =element_line(color ="grey80", size =0.3), # slightly visible major grid linespanel.grid.minor =element_line(color ="grey90", size =0.2), # subtle minor grid linesplot.title =element_text(size =12, face ="bold", hjust =0.5),plot.subtitle =element_text(size =9, hjust =0.5),legend.position ="right" ) +labs(title ="Tree Distribution Across 51 NYC Council Districts",subtitle ="Council Districts shaded by the number of trees recorded within their boundaries" )
With the tree density data by district now prepared, we can turn our attention to exploring it further. This next step focuses on deriving insights and answering key questions about tree distribution, density, and patterns across NYC’s City Council districts.
Key Exploratory Questions
❓Which council district has the most trees?
Interestingly, one of the Staten Island districts appears to have the deepest green, suggesting it may host the most trees. As per the above map, this visual cue hints at where the maximum number of trees might be. Let’s now verify this by calculating the exact counts and identifying which council district truly has the maximum number of trees.
View R Code
# Create summarized data (before turning into datatable)library(dplyr)library(sf)library(DT)library(stringr)# Summarize tree counts by districtmax_tree_count <- nyc_trees_districts_joined |>st_drop_geometry() |>group_by(CounDist) |>summarise(Tree_Count =n()) |>ungroup() |># Add Borough based on council district rangesmutate(Borough =case_when( CounDist >=1& CounDist <=10~"Manhattan", CounDist >=11& CounDist <=18~"Bronx", CounDist >=19& CounDist <=32~"Queens", CounDist >=33& CounDist <=48~"Brooklyn", CounDist >=49& CounDist <=51~"Staten Island",TRUE~"Other" )) |>arrange(desc(Tree_Count)) |>select(Borough, `Council District`= CounDist, Tree_Count) # Move Borough to the first column# Pull the first (top) district and its counttop_district <- max_tree_count$`Council District`[1]top_borough <- max_tree_count$Borough[1]top_tree_count <-format(max_tree_count$Tree_Count[1], big.mark =",")# Create datatable with formatted outputmax_tree_count |>format_titles() |>datatable(options =list(searching =FALSE, info =FALSE)) |>formatRound(c('Tree Count'), 0) |>formatStyle(0, target ='row', backgroundColor =styleEqual(1:5, '#32CD32'))
🌳 Council District 51 located in Staten Island has the highest number of trees in New York City, with approximately 70,927 trees.
❓Which council district has the highest density of trees? The Shape_Area column from the district shape file will be helpful here.
To determine which council district has the highest tree density, we calculated the number of trees per unit area using the Shape_Area column from the district shapefile. By dividing the total number of trees in each district by its corresponding area, we could compare tree density across districts and determine which district has the most densely distributed tree population.
View R Code
library(dplyr)library(DT)# Conversion factor: 1 m² = 3.861e-7 square milesm2_to_sq_miles <-3.861e-7district_tree_density <- nyc_trees_districts_joined |>st_set_geometry(NULL) |>group_by(CounDist) |>summarise(Tree_Count =n(),`Council District Area (Sq. Miles)`=first(Shape_Area) * m2_to_sq_miles ) |>ungroup() |>mutate(`Tree Density (Per Sq. Mile)`= Tree_Count /`Council District Area (Sq. Miles)`,Borough =case_when( CounDist >=1& CounDist <=10~"Manhattan", CounDist >=11& CounDist <=18~"Bronx", CounDist >=19& CounDist <=32~"Queens", CounDist >=33& CounDist <=48~"Brooklyn", CounDist >=49& CounDist <=51~"Staten Island",TRUE~"Other" ),`Council District`= CounDist ) |>select(Borough, `Council District`, Tree_Count, `Council District Area (Sq. Miles)`, `Tree Density (Per Sq. Mile)`) |>arrange(desc(`Tree Density (Per Sq. Mile)`))# Highlight top districtstop_density_district <- district_tree_density$`Council District`[1]highest_density <-round(district_tree_density$`Tree Density (Per Sq. Mile)`[1], 2)highest_density_tree_count <-format(district_tree_density$Tree_Count[1], big.mark =",")# Display datatabledistrict_tree_density |>format_titles() |>datatable(options =list(searching =FALSE, info =FALSE)) |>formatRound(c("Tree Density (Per Sq. Mile)", "Council District Area (Sq. Miles)"), 2) |>formatRound("Tree Count", 0) |>formatStyle(0, target ='row', backgroundColor =styleEqual(1:5, '#32CD32'))
🌳 District 7 tops the city in tree coverage, with a density of 729.18 trees per square mile of area, totaling 15,537 trees.
❓Which district has highest fraction of dead trees out of all trees?
This code groups all trees by council district, counts the total trees and how many are dead, and calculates the fraction/ ratio of dead trees in each district. Finally, it sorts the results to identify the district with the highest proportion of dead trees.
View R Code
library(dplyr)library(DT)# Compute fraction of dead trees per districtdead_tree_fraction <- nyc_trees_districts_joined |>st_drop_geometry() |>group_by(CounDist) |>summarise(Total_Trees =n(),Dead_Trees =sum(tpcondition =="Dead", na.rm =TRUE),Fraction_Dead = Dead_Trees / Total_Trees ) |>ungroup() |>mutate(Borough =case_when( CounDist >=1& CounDist <=10~"Manhattan", CounDist >=11& CounDist <=18~"Bronx", CounDist >=19& CounDist <=32~"Queens", CounDist >=33& CounDist <=48~"Brooklyn", CounDist >=49& CounDist <=51~"Staten Island",TRUE~"Other" ),`Council District`= CounDist,`Percentage Of Dead Trees`=round(Fraction_Dead *100, 2) ) |>select(Borough, `Council District`, Total_Trees, Dead_Trees, Fraction_Dead, `Percentage Of Dead Trees`) |>arrange(desc(Fraction_Dead))# Top district infomost_dead_trees_district <- dead_tree_fraction$`Council District`[1]highest_fraction_dead <- dead_tree_fraction$`Percentage Of Dead Trees`[1]most_dead_trees_count <-format(dead_tree_fraction$Dead_Trees[1], big.mark =",")most_dead_borough <- dead_tree_fraction$Borough[1]# Display in datatabledead_tree_fraction |>format_titles() |>datatable(options =list(searching =FALSE, info =FALSE)) |>formatRound(c('Total Trees', 'Dead Trees'), 0) |>formatRound('Fraction Dead', 4) |>formatRound('Percentage Of Dead Trees', 2) |>formatStyle(0, target ='row', backgroundColor =styleEqual(1:5, '#FF0000'))
🌳 Council District 32 (Queens) has the highest fraction of dead trees, with approximately 14.22% of its trees (4,304) recorded as dead.
❓What is the most common tree species in Manhattan?
Let us identify the most common tree species found in Manhattan using our joined tree–district dataset. We first classify each council district into its respective borough using the case_when() function and then proceed to find the most common species across each borough.
View R Code
# Add a 'Borough' column based on council district rangesnyc_trees_districts <- nyc_trees_districts_joined |>mutate(Borough =case_when( CounDist >=1& CounDist <=10~"Manhattan", CounDist >=11& CounDist <=18~"Bronx", CounDist >=19& CounDist <=32~"Queens", CounDist >=33& CounDist <=48~"Brooklyn", CounDist >=49& CounDist <=51~"Staten Island",TRUE~"Other" ))# Filter for Manhattan and count tree speciesmanhattan_species <- nyc_trees_districts |>filter(Borough =="Manhattan") |>st_drop_geometry() |>group_by(`Tree Species (Genetic Name)`= genusspecies) |>summarise(Tree_Count =n()) |>arrange(desc(Tree_Count))# Find the most common species found in Manhattanmost_common_species <- manhattan_species$`Tree Species (Genetic Name)`[1]most_common_species_count <-format(manhattan_species$Tree_Count[1], big.mark =",")# View result in a datatablemanhattan_species |>format_titles() |>datatable(options =list(searching =FALSE, info =FALSE)) |>formatRound(c('Tree Count'), 0) |>formatStyle(0, target ='row', backgroundColor =styleEqual(1:5, '#32CD32'))
🌳 The most common tree species in Manhattan is the Gleditsia triacanthos var. inermis - Thornless honeylocust, which dominates the borough’s urban canopy with approximately 17,311 trees.
❓What is the species of the tree closest to Baruch’s campus?
To find the tree species closest to Baruch College’s campus, we’ll create a spatial point for Baruch’s location and compute the distance between this point and all trees in our dataset.
View R Code
# Function to create a spatial point (Baruch College location)library(sf)library(dplyr)library(DT)nyc_trees_districts <- nyc_trees_districts_joined |>mutate(Borough =case_when( CounDist >=1& CounDist <=10~"Manhattan", CounDist >=11& CounDist <=18~"Bronx", CounDist >=19& CounDist <=32~"Queens", CounDist >=33& CounDist <=48~"Brooklyn", CounDist >=49& CounDist <=51~"Staten Island",TRUE~"Other" ))new_st_point <-function(lat, lon) {st_sfc(st_point(c(lon, lat)), crs ="WGS84")}# Coordinates for Baruch Collegebaruch_point <-new_st_point(lat =40.7404, lon =-73.9832)# Calculate distances from each tree to Baruchtrees_near_baruch <- nyc_trees_districts |>filter(Borough =="Manhattan") |>mutate(distance =round(as.numeric(st_distance(geometry, baruch_point)), 2)) |>arrange(distance)# Find the nearest speciesnearest_species <- trees_near_baruch$genusspecies[1]nearest_distance <- trees_near_baruch$distance[1]# Prepare datatable with selected and renamed columnstrees_near_baruch |>slice_min(order_by = distance, n =10) |>st_set_geometry(NULL) |># drop geometry for datatableselect(`Tree Species (Genetic Name)`= genusspecies,`Council District`= CounDist, Borough,`Distance (in meters)`= distance,`Tree Condition`= tpcondition ) |>datatable(options =list(searching =FALSE, info =FALSE, pageLength =5)) |>formatStyle(0, target ='row', backgroundColor =styleEqual(1, 'lightblue'))
🌳 The tree closest to Baruch College is a Liquidambar styraciflua - sweetgum, located just about 5.86 meters from campus.
Government Project Design
Green Corridors of District 3: Renewal & Resilience Strategy
This proposal outlines the Green Corridors of District 3: Renewal & Resilience Strategy, a targeted plan to strengthen urban canopy health, enhance neighborhood livability, and prioritize data-driven tree stewardship across the district.
Proposal to NYC Parks Department
District 3 is home to some of Manhattan’s most dynamic neighborhoods, yet its tree canopy has not kept pace with the growing needs of its residents, aging infrastructure, and intensifying climate pressures. The Tree Renewal & Resilience Program seeks dedicated investment to strengthen the district’s environmental resilience, improve pedestrian safety, and expand equitable access to green spaces. This initiative focuses on restoring damaged tree stock, planting new trees in low-coverage zones, and ensuring long-term maintenance of vulnerable species.
Project Overview
This program proposes a district-wide improvement effort centered on three major components:
Replacing hazardous or dead trees that threaten sidewalks, buildings, and public safety.
Planting new, climate-resilient trees in blocks with the lowest canopy cover to reduce heat exposure and improve air quality.
Launching a community stewardship campaign to support maintenance and long-term survival.
This project directly supports the City’s broader goals of climate adaptation, public health, and environmental equity.
Project Scope (Quantitative Goals)
District 3 will undertake the following measurable actions:
3,364 tree removals of dead or high-risk trees (based on tpcondition and riskrating).
2,835 stump eliminations, converting unused stumps into viable planting locations.
541 new tree plantings using species selected for resilience, canopy growth, and maintenance feasibility.
Coverage improvement goal: Increase tree density in low-coverage subregions by 15% (equivalent to 1,800 new plantations) over the next two planting seasons.
Why District 3?
District 3 exhibits urgent environmental needs compared with peer districts.
Key comparative indicators:
District 3 has one of the lowest tree densities among nearby districts such as District 2, District 4, and District 5.
It has a higher proportion of dead or poor-condition trees, indicating overdue maintenance.
Several blocks show above-average stump counts, representing missed planting opportunities.
The district has fewer large-canopy species, limiting shade and heat-mitigation potential relative to comparable districts.
These comparisons clearly demonstrate that District 3 not only requires investment but also has the highest potential for measurable improvement with targeted funding.
Zoomed-In Tree Map of District 3
Note
Extra-credit#1
This visualization highlights the exact areas where replacement, removal, or planting efforts will be prioritized.
Supporting Visualizations
District 3 shows a moderate number of trees in good condition compared with neighboring districts but still has a higher proportion of poor and dead trees relative to total density. This underscores the need for targeted maintenance and strategic planting to improve overall tree health and canopy coverage in the district.
This indicates that while District 3 has moderate biodiversity, there is still significant potential to increase species diversity through targeted plantings, enhancing ecological resilience and canopy coverage.
View R Code
library(ggplot2)library(dplyr)library(sf)# Count unique species per district (2-5)species_summary <- nyc_trees_districts_joined |>filter(CounDist %in%2:5) |>st_drop_geometry() |>group_by(CounDist) |>summarise(unique_species =n_distinct(genusspecies),.groups ="drop" ) |>mutate(highlight =ifelse(CounDist ==3, "District 3", "Other Districts") )# Horizontal bar chart with labelsggplot(species_summary, aes(x =reorder(factor(CounDist), unique_species), y = unique_species, fill = highlight)) +geom_col(width =0.6) +geom_text(aes(label = unique_species), hjust =-0.1, # slightly outside the barsize =4, color ="black") +coord_flip() +# horizontal barsscale_fill_manual(values =c("District 3"="#1b9e77", "Other Districts"="#a8a8a8")) +labs(title ="Biodiversity Snapshot: Unique Tree Species Across Districts 2–5",x ="District",y ="Number of Unique Species",fill ="" ) +theme_minimal() +theme(plot.title =element_text(size =12, face ="bold", hjust =0.5),axis.title.x =element_text(size =12),axis.title.y =element_text(size =12),axis.text =element_text(size =11),legend.position ="none" ) +expand_limits(y =max(species_summary$unique_species) *1.1) # add extra space for labels
These graphics demonstrates the clear quantitative need for immediate action.
District Comparison Visualizations
District 3 in Manhattan has one of the lowest tree densities among local council districts, which means that allocating funds to tree programs here offers the potential for highly visible, high-impact improvements in urban greenery. This comparison underscores the urgent need for prioritizing tree investments in District 3 to maximize public and environmental benefits.
Districts 2 and 3 exhibit similar spatial patterns in tree conditions. While the majority of trees are in good condition, both districts contain localized clusters of poor-condition trees, indicating areas that may benefit from targeted maintenance or intervention.
Conclusion & Call to Action
Investing in the Urban Tree Renewal & Resilience Program in District 3 is a cost-effective, community-centered step toward a greener, healthier, and safer Manhattan. With strategic funding, we can transform neglected streetscapes, reduce heat vulnerability, improve air quality, and set a model for district-level environmental planning.
We respectfully request that NYC Parks allocate additional budgetary support for this program in the upcoming cycle, enabling District 3 to become a leading example of sustainable, community-driven urban forestry.